﻿/*  Copyright (C) 2013 AmaroK86 (marcidm 'at' hotmail 'dot' com)
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.

 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.

 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AmaroK86.MassEffect3.PCC;
using AmaroK86.MassEffect3.Image;
using Gibbed.IO;

namespace AmaroK86.MassEffect3.UnrealObjects
{
    public struct ImageInfo
    {
        public enum storage
        {
            arcCpr = 0x3, // archive compressed
            arcUnc = 0x1, // archive uncompressed (DLC)
            pccSto = 0x0, // pcc local storage
            empty = 0x21  // unused image (void pointer)
        }

        public storage storageType;
        public int uncSize;
        public int cprSize;
        public int offset;
        public byte[] pccImage;
        public ImageSize imgSize;
    }

    public class Texture2D : UnrealObject // fully tested Texture2D class
    {
        int numMipMaps { get { return (imgList == null) ? 0 : imgList.Count; } }
        byte[] footerData;

        public string textureName { get { return exportRef.ObjectName; } }
        public string Extension
        {
            get
            {
                switch ((string)properties["Format"].Value)
                {
                    case "PF_DXT1":
                    case "PF_DXT5":
                    case "PF_V8U8":
                        return "dds";
                    case "PF_A8R8G8B8":
                    case "PF_G8":
                        return "tga";
                    default:
                        throw new InvalidDataException("Invalid image format: " + (string)properties["Format"].Value);
                }
            }
        }
        public string archiveName
        {
            get
            {
                if (properties.exist("TextureFileCacheName"))
                    return (string)properties["TextureFileCacheName"].Value;
                else
                    return null;
            }
        }
        ImageList mImgList;
        public ImageList imgList { get { return mImgList; } set { mImgList = value; updateValues(); } } // image list

        public class ImageList : List<ImageInfo>
        {
            public ImageInfo this[ImageSize searchImgSize]
            {
                get
                {
                    int idx = this.FindIndex(image => image.imgSize == searchImgSize);
                    if (idx != -1)
                        return this[idx];
                    else
                        throw new KeyNotFoundException("Invalid size \"" + searchImgSize + "\"");
                }
                set
                {
                    int idx = this.FindIndex(image => image.imgSize == searchImgSize);
                    if (idx != -1)
                        this[idx] = value;
                    else
                        throw new KeyNotFoundException("Invalid size \"" + searchImgSize + "\"");
                }
            }

            public ImageSize maxImgSize { get { return this.Max(image => image.imgSize); } }
            public ImageSize maxUsefulSize { get { return this.Where(image => image.storageType != ImageInfo.storage.empty).Max(image => image.imgSize); } }
        }

        public Texture2D(PCCObject.ExportEntry export)
            : base(export)
        {
            if (data.Length == 0) // check if exists image data
                throw new FormatException("Images data not found on Texture2D object");

            MemoryStream dataStream = new MemoryStream(data);
            uint count = dataStream.ReadValueU32();

            mImgList = new ImageList();
            while (dataStream.Position < dataStream.Length && count > 0)
            {
                ImageInfo imgInfo = new ImageInfo();
                imgInfo.storageType = (ImageInfo.storage)dataStream.ReadValueS32();
                imgInfo.uncSize = dataStream.ReadValueS32();
                imgInfo.cprSize = dataStream.ReadValueS32();
                imgInfo.offset = dataStream.ReadValueS32();
                if (imgInfo.storageType == ImageInfo.storage.pccSto)
                {
                    imgInfo.offset = (int)(export.DataOffset + dataOffset + dataStream.Position); // saving pcc offset as absolute offset
                    imgInfo.pccImage = new byte[imgInfo.uncSize];
                    imgInfo.pccImage = dataStream.ReadBytes(imgInfo.pccImage.Length);
                }
                imgInfo.imgSize = new ImageSize(dataStream.ReadValueU32(), dataStream.ReadValueU32());
                //if (imgInfo.storageType != storage.empty)// || ((string)properties["Format"].Value).ToString() == "PF_V8U8")
                    mImgList.Add(imgInfo);
                /*else
                {
                    //properties["MipTailBaseIdx"].Value = (int)properties["MipTailBaseIdx"].Value - 1;
                    //properties["SizeX"].Value = (int)((int)properties["SizeX"].Value / 2);
                    //properties["SizeY"].Value = (int)((int)properties["SizeY"].Value / 2);
                    //properties["OriginalSizeX"].Value = (int)((int)properties["OriginalSizeX"].Value / 2);
                    //properties["OriginalSizeY"].Value = (int)((int)properties["OriginalSizeY"].Value / 2);
                }*/
                //imgList.Add(imgInfo);
                count--;
            }

            //updateValues();

            // save what remains
            int remainingBytes = (int)(dataStream.Length - dataStream.Position);
            footerData = new byte[remainingBytes];
            dataStream.Read(footerData, 0, footerData.Length);
        }

        private void updateValues()
        {
            ImageSize maxImage = mImgList.maxImgSize;
            if (properties.exist("OriginalSizeX"))
                properties["OriginalSizeX"].Value = (int)maxImage.width;
            if (properties.exist("OriginalSizeY"))
                properties["OriginalSizeY"].Value = (int)maxImage.height;
            if(properties.exist("MipTailBaseIdx"))
                properties["MipTailBaseIdx"].Value = getMipMapVal();
            properties["SizeX"].Value = (int)maxImage.width;
            properties["SizeY"].Value = (int)maxImage.height;
        }

        private int getMipMapVal()
        {
            ImageSize maxImage = mImgList.maxImgSize;
            return (int)Math.Log(Math.Max(maxImage.width, maxImage.height), 2);
        }

        public void removeMaxTexture()
        {
            if (imgList.Count <= 1)
                return;

            imgList.RemoveAt(mImgList.Count - 1);
            updateValues();
        }

        public void addMaxTexture(ImageInfo newImage)
        {
            if (imgList.Count <= 1)
                return;

            if (newImage.imgSize / 2 == mImgList.Max(image => image.imgSize))
            {
                imgList.Add(newImage);
                updateValues();
            }
        }

        public void replaceTexture(ImageInfo newImage)
        {
            mImgList[newImage.imgSize] = newImage;
        }

        public override void commitChanges()
        {
            MemoryStream buffer = new MemoryStream();

            //write unknown header data
            buffer.WriteBytes(headerData);
            //write properties
            buffer.WriteBytes(properties.ToByteArray());
            //write data
            buffer.WriteValueS32(numMipMaps);
            foreach (ImageInfo imgInfo in mImgList)
            {
                buffer.WriteValueS32((int)imgInfo.storageType);
                buffer.WriteValueS32(imgInfo.uncSize);
                buffer.WriteValueS32(imgInfo.cprSize);
                buffer.WriteValueS32(imgInfo.offset);
                if (imgInfo.storageType == ImageInfo.storage.pccSto)
                {
                    buffer.Write(imgInfo.pccImage, 0, imgInfo.pccImage.Length);
                }
                buffer.WriteValueU32(imgInfo.imgSize.width);
                buffer.WriteValueU32(imgInfo.imgSize.height);
            }
            // Texture2D footer, 24 bytes size
            buffer.WriteBytes(footerData);

            exportRef.Data = buffer.ToArray();
        }
    }
}
